1 JS是什么
1.1 概论
- 解释执行:轻量级解释性的
- 语言特点:
- 动态(JAVA运行时编译成class,编译成CLASS后就不能更改,不过js是动态的可以随时更改)
- 头等函数(意味着函数是JS中的一等公民,函数会先执行,比如给变量赋值会被提升先执行)
- 执行环境:在宿主环境下运行(类似于Java在JVM中运行),浏览器只最常见的JS宿主环境,其次就是node环境
- 浏览器中JS组成:
- ECMAScrip:语法规范
- 变量,数据类型,类型转换,操作符
- 流程控制语句:判断,循环语句
- 数组,函数,作用于,预解析
- 对象,属性,方法,简单类型和复杂类型的区别
- 内置对象:Math,Data,Array,基本包装类型String,Number,Boolean
- BOM
- onload页面加载时间,window顶级对象
- 定时器
- location,history
- DOM
- 获取页面元素,注册事件
- 属性操作,样式操作
- 属性属性,节点层级
- 动态创建元素
- 事件:注册事件的方式,事件的三个阶段(捕获,冒泡,执行),事件对象
- ECMAScrip:语法规范
1.2 浏览器工作原理
浏览器的主要组件包括:
1. 用户界面 - 包括地址栏、后退/前进按钮、书签目录等,也就是你所看到的除了用来显示你所请求页面的主窗口之外的其他部分。
2. 浏览器引擎 - 用来查询及操作渲染引擎的接口。
3. 渲染引擎 - 用来显示请求的内容,例如,如果请求内容为html,它负责解析html及css,并将解析后的结果显示出来。
4. 网络 - 用来完成网络调用,例如http请求,它具有平台无关的接口,可以在不同平台上工作。
5. UI后端 - 用来绘制类似组合选择框及对话框等基本组件,具有不特定于某个平台的通用接口,底层使用操作系统的用户接口。
6. JS解释器 - 用来解释执行JS代码。
7. 数据存储 - 属于持久层,浏览器需要在硬盘中保存类似cookie的各种数据,HTML5定义了web database技术,这是一种轻量级完整的客户端存储技术

- User Interface 用户界面,我们所看到的浏览器
- Browser engine 浏览器引擎,用来查询和操作渲染引擎
- *Rendering engine 用来显示请求的内容,负责解析HTML,CSS并把解析的内容显示出来
- Networking 网络,负责发送网络请求
- *JavaScript Interpreter(解析者) JavaScript解析器,负责执行JS代码
- UI Backend UI后端,用来绘制类似组合框的弹出窗口
- Data Persistence(持久化) 数据持久化,数据存储 cookie,HTML5中的sessionStorage
具体详解可以看这里(https://www.cnblogs.com/wjlog/p/5744753.html)
2 JS代码执行过程
2.1 小案例
我们看如下代码1
2
3
4
5
6
7var num = 10;
function fn() {
console.log(num);
num = 20;
}
console.log(num);
fn();
这段代码会怎么输出?
先输出10再输出还是10;
1 | var num = 10; |
这段代码会怎么输出?
先输出10再输出undefined;
为什么会这样,这就是JS的特性,定义变量提升
上面的代码可以这么写
1 | var num; |
2.2 JS过程说明
JS运行分两个阶段
- 预解析
- 全局解析(所有变量和函数声明都会提前:同名的函数和变量函数的优先级高)
- 函数内部预解析(所有变量,函数和形参都会参与预解析)
- 预解析的内容包括
- 函数
- 形参
- 普通变量
- 执行
先预解析全局作用域,然后执行全局作用域中的代码
在执行全局代码的过程中遇到函数调用就会先进行函数预解析,然后再执行函数内代码
3 JS面向对象
3.1 什么是对象
万物皆对象
对象是单个事物的抽象。
- 比如这个汽车,他就是一个对象
对象是一个容器,封装了属性和方法
- 比如汽车有颜色,这就是一个属性
- 比如汽车能开,这就是一个方法
在实际开发中,对象是一个抽象的概念,可以将其简单理解为:数据集或功能集
ES中把对象定义为:无序属性的集合,其属性可以包含基本值,对象或者函数
提示:每个对象都是基于一个引用类型创建的(比如汽车,泛指世界上所有汽车),这些类型可以是系统内置的原生类型,也可以是开发人员自己定义的类型。
3.2 什么是面向对象
是过程形式代码的一种高度封装,目的在于提高代码的开发效率和可维护性。
是一种编程思想,它将真实世界的各种复杂关系抽象为一个个对象,然后由对象之间的分工与合作,完成对真实世界的模拟。
在面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工,可以完成接受信息,处理数据,发出信息等任务。
因此,面向对象编程具有灵活,代码可复用,高度模块化等特点,容易维护和开发,比起由一系列函数或指令组成的传统的过程式编程,更适合多人合作的大型软件项目。
面向对象与面向过程
- 面向过程就是亲力亲为,事无巨细,面面俱到,步步紧跟,有条不紊
- 面向对象就是找一个对象,指挥得到结果
- 面向对象执行者转变指挥者
- 面向对象不是面向过程的替代,而是面向过程的封装
面向对象的特点: - 封装性
- 继承性
- 抽象(多态Animal animal = new Dog())
3.3 封装对象
1 | // 第一代传递每个属性 |
1 | // 第一种方式创建对象 |
3.4 构造函数
1 | function Person(name,age) { |
结构构造函数代码的执行
Person和createStudent的不同之处
- 没有显示创建对象
- 直接将属性和方法都赋给了this对象
- 没有return语句
- 函数名使用的是大写的Person
而要创建Person实例,则必须使用new操作符。
以这种方式调用构造函数会经历以下4步骤:
- 创建一个新对象
- 将构造函数的作用域赋值给新对象(因此this就指向了这个新对象)
- 执行构造函数中的代码
- 返回新对象
3.5 构造函数和实例对象
- constructor
使用构造函数的好处不仅仅在于代码的间接性,更重要的是我们可以识别对象的具体类型
在每一个实例对象中都有一个constructor属性,该属性指向创建该实例的构造函数;
1 | console.log(p1.constructor === Person); //true |
- instanceof
对象的constructor属性最初是用来标识对象的类型的。
但是,如果要检测对象的类型,还是使用instanceof操作符更可靠一些。
1 | console.log(p1 instanceof Person); //true |
- typeof
typeof只能获取基本类型
复杂类型通过typeof获取到的始终是object
总结:
- 构造函数是根据具体的事务抽象出来的抽象模板
- 实例对象是根据抽象的构造函数模板得到的具体实例对象
- 每一个实例对象都具有一个 constructor 属性,指向创建该实例的构造函数。
3.6 原型对象
1 | function Person(name,age) { |
这段代码表面上看没有问题,但是实际上这样做,有一个很大的弊端。
那就是对于每一个实例对象,type和sayName都是一模一样的内容。
每一次生成一个实例,都必须为重复的内容,多占用一些内存,如果实例对象很多,会造成内存浪费。
1 | function Person(name,age) { |
原型对象有一个属性prototype
可以通过构造函数的原型对象,给对象增加成员
1 | function Person(name,age) { |
- 这意味着,我们可以把所有对象所需要共享的属性和方法直接定义在
prototype对象上 - 任何函数都具有一个
prototype属性,该属性是一个对象 - 构造函数的
prototype对象魔人都有一个constructor属性,只想prototype对象所在函数
3.7 原型链
如果一个对象中没有这个属性或方法,JS会去它的原型中去找,如果还没有则去更上一级去找直到最父级null;
每当代码读取某个对象的某个属性时,都会执行一侧搜索,目标是给定名字的属性
- 搜索首先从对象实例本身开始
- 如果在实例中找到了具有给定名字的原型对象,则返回该属性的值。
- 如果没有找到,则继续搜所指针指向的原型对象,在原型对象中查找具有给定名字的属性
- 如果在原型对象中找到了这个属性,则返回该属性的值
也就是说,我们在调用person1.sayName()的时候,会先后执行两次搜所。 - 首先,解释器会问:“实例persion有sayName属性吗?”答:“没有”。
- 然后,他继续搜所,再问:“persion1的原型有sayName属性吗?”答:“有”。
- 于是,他就读取那个保存在原型对象中的函数。
- 当我们调用persion2.sayName()时,将会重现相同的搜所过程,得到相同的结果。
而这正是多个对象实例共享原型所保存的属性和方法的基本原理。
1 | function Person(name,age) { |

把上诉代码进行修改,改成只在p1上进行修改属性
1 | function Person(name,age) { |
3.7 更简单的原型语法
我们注意到,前面例子中每添加一个属性和方法都需要敲一遍Persion.prototype。
为了减少不必要的输入,更常见的做法是用一个包含所有属性的方法的对象字面量来重写整个原型对象
1 | function Person(name,age) { |
在该实例中,我们将Persion.prototype重置到了一个新的对象。
这样做的好处是Persion.prototype为了添加成员更简单了,但是也会带来一个问题,那就是原型对象丢失了constructor成员
所以,我们为了保持constructor的正确指向,代码如下:
1 | function Person(name,age) { |
案例
- 求数组中所有数的和
1 | Array.prototype.getSum = function() { |
- 字符串首字母大写
1 | String.prototype.toUpper = function() { |
3.8 面向对象案例
- 随机闪烁方块
方块对象(box.js)
1 | // 构造函数BOX |
生成随机数的类(tool.js)
1 | var Tool = { |
主函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24var map = document.querySelector(".map");
// 生成10个盒子
var arr = [];
for(var i = 0;i<10;i++){
var r = Tool.getRandom(0,255);
var g = Tool.getRandom(0,255);
var b = Tool.getRandom(0,255);
var box = new Box({
backgroundColor:"rgb(' " + r + "','" + g + "','" + b + "')"
});
box.render(map);
arr.push(box);
}
random();
// 定时生成随机盒子的位置
setInterval(random,500)
function random() {
arr.forEach(function(item) {
item.random();
})
}
3.9 继承
1. 原型继承
基本思想:利用原型让一个引用类型继承另一个引用类型的属性和方法,即让原型对象等于另一个类型的实例
1 | function Person() { |
- 原型继承存在问题
- 无法设置构造函数的参数
- 属性时通过原型继承的,原型上的成员都是共享的
2. 借用构造函数
基本思想:在子类型构造函数的内部调用超类型构造函数,通过使用apply()和call()方法可以在将来新创建的对象上执行构造函数
1 | function Person(name,age) { |
3. 组合继承
原型继承+借用构造函数
基本思想:将原型链和借用构造函数技术组合到一起。使用原型链实现对原型属性和方法的继承,用借用构造函数模式实现对实例属性的继承。这样既通过在原型上定义方法实现了函数复用,又能保证每个实例都有自己的属性
1 | function Person(name,age) { |
4 函数进阶
4.1 函数的定义方式
1. 函数声明
1 | function fn() { |
2. 函数表达式
1 | var fun = function() { |
3. new Function
1 | var fn = new Function("a","b","console.log(a + b)"); |
4.2 声明和表达式的区别
- 函数声明会提升,表达式(只提升var fn)不会提升
1 | // 函数声明 |
- 在if中的变量提升
- 在新版本的浏览器中可以根据条件定义函数
- 在新版本的浏览器中在条件语句中定义函数不会提升
- 但在老版本的ie中,条件语句中定义函数也会进行提升
1
2
3
4
5
6
7
8
9
10if(true){
function fn() {
console.log("true")
}
}else{
function fn() {
console.log("false")
}
}
fu(); // true
老版本需要这样写
1 | var fn; |
4.2 this
- 函数中的this window
- 方法中的this 调用方法的对象
- 构造函数中的this 通过构造函数创建的对象
- 事件处理函数中的this 触发事件的对象
- 定时器中性的function中的this window
function中的this最终是由function调用时候决定的
1 | var o = { |
1. call
修改this指向,参数用,号分隔开
1 | function fn(a,b) { |
2. apply
1 | function fn(a,b) { |
修改this指向,参数用数组包装起来
案例
利用Math.max求数组的最大值
Math.max接受的参数是用,号分割的一些数1
2
3
4
5
6
7
8
9
var max = Math.max(5,6,1,9);
console.log(max);
var arr = [34,1,19,10];
var max = Math.max.apply(Math,arr) // 这里不需要改变this所以还是指向本身
console.log(max);
console.log.apply(console,arr); // 效果等同于 console.log(34,1,19,10)
操作维数组
1 | var obj = { |
3. bind
修改方法中的this1
2
3
4
5
6
7var o = {
name:"zs"
};
var btn = document.getElementById("btn");
btn.onclick = function() {
}.bind(o)
4.3 函数的成员
1 | function fn() { |
arguments经常用于可变参数
自己写一个max最大值1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17function max() {
var max = arguments[0];
// 借用数组方法实现(arguments是一个伪数组)
// Array.prototype.forEach.call(arguments,function(item) {
// console.log(item);
// })
for(var i = 1;i<arguments.length;i++){
if(max < arguments[i]){
max = arguments[i];
}
}
return max;
}
var m = max(1,2,3);
console.log(m);
4.4 高级函数
所谓的高级函数就是函数作为参数,或者函数作为返回值
1. 函数作为参数
- 数组的forEach循环
- 定时器setInterval
- 绑定事件addEventListener
- 排序sort
自定义排序规则(默认排序按字母排序)1
2
3
4
5
6
7
8
9
10
11
var arr = ["a","s","z","c"];
console.log(arr.sort()); //["a", "c", "s", "z"]
var arr2 = [11,111,10,20,50];
console.log(arr2.sort()); // [10, 11, 111, 20, 50]
arr2.sort(function(n1,n2) {
return n1 - n2;
});
console.log(arr2); // [10, 11, 20, 50, 111]
2. 函数作为返回值
1 | // 100 + N |
获取基本类型方法
自定义类型不可以,因为所有自定义类型的toString()返回的都是[object object]1
2
3
4
5
6
7
8
9
10
11function getFun(type) {
return function(obj) {
return Object.prototype.toString.call(obj) === type;
}
}
var isArray = getFun('[object Array]')
var isObject = getFun('[object object]')
console.log(isArray([]));
console.log(isObject({}));
获取随机数,第一次随机获取,之后每次获取的数都和第一次一样1
2
3
4
5
6
7
8
9
10
11
12
13
function getRandom() {
var random = Math.random() * 10 + 1;
return function() {
return random;
}
}
var fun = getRandom();
console.log(fun());
console.log(fun());
console.log(fun());
4.5 闭包
闭包可以在调试的Scope中看到
可以通过打断点观察到
正常函数在调用之后内存会释放资源,而闭包不会,因为它不知道还需要在什么地方需要调用
案例1
1 | <ul id="ul"> |
1 | var list = document.querySelectorAll("#ul li"); |
这样无论点击那个弹出的都是最后一个
之前我们会给li设置一个属性来解决这个问题
1 | var list = document.querySelectorAll("#ul li"); |
现在我们可以用闭包的方式来解决
创建调用一个自调用函数
1 | var list = document.querySelectorAll("#ul li"); |
案例21
2
3
4
5
6
7console.log(111)
for (var i = 0;i < 3;i++){
setTimeout(function() {
console.log(i)
},0)
}
console.log(222)
这段代码输出顺序为:111》222》3》3》3
先执行主线程之后再执行延时函数
1 | console.log(111) |
这段代码输出顺序为:111》222》0》1》0
案例3
1 | var name = "The Window"; |
输出内容为 The Window
因为window的name属性是空,但在开始定义了一个name变量,即是定义给window的name
之后object.getNameFunc()返回了一个函数,再之后是window调用了这个返回的函数,故this是window,this.name是The Window。
这里并没出现闭包
1 | var name = "The Window"; |
输出内容为 My Object
这里出现了闭包,调用的是object的name
案例4
按钮设置字体大小
1 | <body> |
1 | var btn1 = document.getElementById("btn1"); |
最后更新: 2019年02月26日 20:23